const Aria2 = require("../shared/aria2/index");
const lodash = require("lodash");
const { isEmpty, clone } = lodash;
const constants = require("../shared/constants");
const { ENGINE_RPC_HOST } = constants;
const ConfigManager = require("../config/engineManager");
const { logger } = require("ee-core/log");
const { Service } = require("ee-core");
const _configManager = new ConfigManager();
const {
  separateConfig,
  compactUndefined,
  formatOptionsForEngine,
  mergeTaskResult,
  changeKeysToCamelCase,
  changeKeysToKebabCase,
} = require("../shared/utils/index");

class EngineClientService extends Service {
  constructor(ctx) {
    super(ctx);
    
    this.config = {
      ..._configManager.userConfig.getItem("user"),
      ..._configManager.systemConfig.getItem("system"),
    };
    this.client = this.initClient();
  }
  setOptions(options){
    this.options = options;
  }
  // 初始化
  initClient() {
    // const { rpcListenPort: port, rpcSecret: secret } = this.config;
    const host = ENGINE_RPC_HOST;
    // logger.info('rpcListenPort', this.config)
    return new Aria2({
      host,
      port:  this.config['rpc-listen-port'],
      secret: this.config['rpc-secret'],
    });
  }
  // 关闭
  closeClient() {
    this.client
      .close()
      .then(() => {
        this.client = null;
      })
      .catch((err) => {
        logger.error("engine client close fail", err);
      });
  }

  /**
   * 获取配置
   * @returns
   */
  fetchPreference() {
    return this.config;
  }

  /**
   * 保存当前配置
   */
  savePreference(params) {
    const { user, system, others } = separateConfig(params);
    let config = {};
    if (!isEmpty(user)) {
      config.user = user;
    }
    if (!isEmpty(system)) {
      config.system = system;
      this.updateActiveTaskOption(system);
    }

    // TODO 存储数据
  }

  getVersion() {
    return this.client.call("getVersion");
  }

  changeGlobalOption(options) {
    const args = formatOptionsForEngine(options);
    return this.client.call("changeGlobalOption", args);
  }

  getGlobalOption() {
    return new Promise((resolve) => {
      this.client.call("getGlobalOption").then((data) => {
        resolve(changeKeysToCamelCase(data));
      });
    });
  }

  getOption(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);

    return new Promise((resolve) => {
      this.client.call("getOption", ...args).then((data) => {
        resolve(changeKeysToCamelCase(data));
      });
    });
  }

  updateActiveTaskOption(options) {
    this.fetchTaskList({ type: "active" }).then((data) => {
      if (isEmpty(data)) {
        return;
      }

      const gids = data.map((task) => task.gid);
      this.batchChangeOption({ gids, options });
    });
  }

  changeOption(params = {}) {
    const { gid, options = {} } = params;

    const engineOptions = formatOptionsForEngine(options);
    const args = compactUndefined([gid, engineOptions]);

    return this.client.call("changeOption", ...args);
  }

  getGlobalStat() {
    return this.client.call("getGlobalStat");
  }

  addUri(params) {
    const { uris, outs, options } = params;
    const tasks = uris.map((uri, index) => {
      const engineOptions = formatOptionsForEngine(options);
      if (outs && outs[index]) {
        engineOptions.out = outs[index];
      }
      const args = compactUndefined([[uri], engineOptions]);
      return ["aria2.addUri", ...args];
    });
    return this.client.multicall(tasks);
  }

  addTorrent(params) {
    const { torrent, options } = params;
    const engineOptions = formatOptionsForEngine(options);
    const args = compactUndefined([torrent, [], engineOptions]);
    return this.client.call("addTorrent", ...args);
  }

  addMetalink(params) {
    const { metalink, options } = params;
    const engineOptions = formatOptionsForEngine(options);
    const args = compactUndefined([metalink, engineOptions]);
    return this.client.call("addMetalink", ...args);
  }

  fetchDownloadingTaskList(params = {}) {
    const { offset = 0, num = 20, keys } = params;
    const activeArgs = compactUndefined([keys]);
    const waitingArgs = compactUndefined([offset, num, keys]);
    return new Promise((resolve, reject) => {
      this.client
        .multicall([
          ["aria2.tellActive", ...activeArgs],
          ["aria2.tellWaiting", ...waitingArgs],
        ])
        .then((data) => {
          logger.log("fetch downloading task list data:", data);
          const result = mergeTaskResult(data);
          resolve(result);
        })
        .catch((err) => {
          logger.log("fetch downloading task list fail:", err);
          reject(err);
        });
    });
  }

  fetchWaitingTaskList(params = {}) {
    const { offset = 0, num = 20, keys } = params;
    const args = compactUndefined([offset, num, keys]);
    return this.client.call("tellWaiting", ...args);
  }

  fetchStoppedTaskList(params = {}) {
    const { offset = 0, num = 20, keys } = params;
    const args = compactUndefined([offset, num, keys]);
    return this.client.call("tellStopped", ...args);
  }

  fetchActiveTaskList(params = {}) {
    const { keys } = params;
    const args = compactUndefined([keys]);
    return this.client.call("tellActive", ...args);
  }

  fetchTaskList(params = {}) {
    const { type } = params;
    switch (type) {
      case "active":
        return this.fetchDownloadingTaskList(params);
      case "waiting":
        return this.fetchWaitingTaskList(params);
      case "stopped":
        return this.fetchStoppedTaskList(params);
      default:
        return this.fetchDownloadingTaskList(params);
    }
  }

  fetchTaskItem(params = {}) {
    const { gid, keys } = params;
    const args = compactUndefined([gid, keys]);
    return this.client.call("tellStatus", ...args);
  }

  fetchTaskItemWithPeers(params = {}) {
    const { gid, keys } = params;
    const statusArgs = compactUndefined([gid, keys]);
    const peersArgs = compactUndefined([gid]);
    return new Promise((resolve, reject) => {
      this.client
        .multicall([
          ["aria2.tellStatus", ...statusArgs],
          ["aria2.getPeers", ...peersArgs],
        ])
        .then((data) => {
          logger.log("fetchTaskItemWithPeers:", data);
          const result = data[0] && data[0][0];
          const peers = data[1] && data[1][0];
          result.peers = peers || [];
          logger.log("fetchTaskItemWithPeers.result:", result);
          logger.log("fetchTaskItemWithPeers.peers:", peers);

          resolve(result);
        })
        .catch((err) => {
          logger.log("fetch downloading task list fail:", err);
          reject(err);
        });
    });
  }

  fetchTaskItemPeers(params = {}) {
    const { gid, keys } = params;
    const args = compactUndefined([gid, keys]);
    return this.client.call("getPeers", ...args);
  }

  pauseTask(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("pause", ...args);
  }

  pauseAllTask(params = {}) {
    return this.client.call("pauseAll");
  }

  forcePauseTask(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("forcePause", ...args);
  }

  forcePauseAllTask(params = {}) {
    return this.client.call("forcePauseAll");
  }

  resumeTask(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("unpause", ...args);
  }

  resumeAllTask(params = {}) {
    return this.client.call("unpauseAll");
  }

  removeTask(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("remove", ...args);
  }

  forceRemoveTask(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("forceRemove", ...args);
  }

  saveSession(params = {}) {
    return this.client.call("saveSession");
  }

  purgeTaskRecord(params = {}) {
    return this.client.call("purgeDownloadResult");
  }

  removeTaskRecord(params = {}) {
    const { gid } = params;
    const args = compactUndefined([gid]);
    return this.client.call("removeDownloadResult", ...args);
  }

  multicall(method, params = {}) {
    let { gids, options = {} } = params;
    options = formatOptionsForEngine(options);

    const data = gids.map((gid, index) => {
      const _options = clone(options);
      const args = compactUndefined([gid, _options]);
      return [method, ...args];
    });
    return this.client.multicall(data);
  }

  batchChangeOption(params = {}) {
    return this.multicall("aria2.changeOption", params);
  }

  batchRemoveTask(params = {}) {
    return this.multicall("aria2.remove", params);
  }

  batchResumeTask(params = {}) {
    return this.multicall("aria2.unpause", params);
  }

  batchPauseTask(params = {}) {
    return this.multicall("aria2.pause", params);
  }

  batchForcePauseTask(params = {}) {
    return this.multicall("aria2.forcePause", params);
  }
}

EngineClientService.toString = () => "[class EngineClientService]";
module.exports = EngineClientService;
